今天我們要將原本寫死的 Todo List 資料轉為使用實際輸入的內容。由於在先前的篇章已經使用過 useState,這次我們將透過 useRef 來提取資料,藉此練習不同的狀態管理方法。
首先,先引入 useRef 並將變數指定給對應的 input 元素:
import { useRef, type FormEvent } from 'react'
export default function CreateTodo() {
const newTodo = useRef()
const createTodoHandler = (event: FormEvent) => {
event?.preventDefault()
}
return (
<form className='w-full mb-[20px] h-fit flex' onSubmit={createTodoHandler}>
<label htmlFor='newTodo'></label>
<input
ref={newTodo}
type='text'
id='newTodo'
name='newTodo'
className='flex-1 mr-[16px] px-3 rounded-[5px] focus:outline-none'
/>
<button>Create</button>
</form>
)
}
當滑鼠移至 ref 上時,可以發現目前的型別是 undefined:
這是因為我們沒有為該 ref 指定初始值,指定初始值後即可解決該報錯:
const newTodo = useRef(null)
接著,在 createTodoHandler 中建立一個 enteredTodo 變數來儲存輸入的內容:
const createTodoHandler = (event: FormEvent) => {
event?.preventDefault()
const enteredTodo = newTodo.current.value
}
這時會出現第一個錯誤訊息:'newTodo.current' is possibly 'null'.。由於我們確定這個值會存在,所以可以在 newTodo 後加上 !,這表示我們保證 newTodo.current 不會是 null。但使用 ! 要特別謹慎,因為當值為 null 時會導致錯誤:
const enteredTodo = newTodo.current!.value
接著,第二個錯誤訊息出現:Property 'value' does not exist on type 'never'.。雖然我們知道 newTodo 會指向 <input> 元素,但 TypeScript 不知道。這時我們需要在 useRef 中傳入正確的型別,即 HTMLInputElement:
const newTodo = useRef<HTMLInputElement>(null)
現在我們已經處理好輸入內容的接收。接下來,我們要讓這些內容真正成為 Todo List 上的項目。 還記得我們在 App.tsx 中的 createTodoHandler 嗎?
const createTodoHandler = () => {
const newTodo: TodoItem = {
id: Math.random(),
title: 'Learn JavaScript',
isFinished: false,
}
setTodos((prevTodos) => [...prevTodos, newTodo])
}
我們要將這個函式傳入 CreateTodo 元件,用來更新 Todo List 的資料。 首先,為 CreateTodo 創建一個 props 的型別:
type CreateTodoProps = {
onCreateTodo: (title: string) => void
}
接著,將 onCreateTodo 放入提交表單的函式中:
const createTodoHandler = (event: FormEvent) => {
event?.preventDefault()
const enteredTodo = newTodo.current!.value
onCreateTodo(enteredTodo)
}
回到 App.tsx,為 createTodoHandler 添加 title 參數,並將寫死的內容替換成傳入的 title:
const createTodoHandler = (title: string) => {
const newTodo: TodoItem = {
id: Math.random(),
title: title,
isFinished: false,
}
setTodos((prevTodos) => [...prevTodos, newTodo])
}
最後,將 createTodoHandler 傳遞給 CreateTodo 元件:
<CreateTodo onCreateTodo={createTodoHandler} />
打開瀏覽器,現在你應該可以根據輸入的內容更新 Todo List:

目前新增的資料後,表單中的舊資料仍會保留在輸入欄位中。為了清空這些舊資料,我們可以使用內建方法 reset() 來重置表單:
event.currentTarget.reset()
錯誤訊息 Property 'reset' does not exist on type 'EventTarget & Element'. 表示 TypeScript 不知道 event 是表單元素。為了解決這個問題,我們需要將事件的型別明確定義為 HTMLFormElement:
const createTodoHandler = (event: FormEvent<HTMLFormElement>) => {
event?.preventDefault()
const enteredTodo = newTodo.current!.value
onCreateTodo(enteredTodo)
event.currentTarget.reset()
}
經過這些設定後,每當新增一個 todo,輸入欄位內的資料將會自動清空。